﻿using System;
using System.Linq;
using System.Reflection;
using PostSharp.Extensibility;
using PostSharp.Laos;

namespace Aspects
{
   /// <summary>
   /// <para>
   /// Attribute that will create a field in the class at the other end of an association and add 
   /// a <see cref="AssociationAttribute"/> to each end of the association.
   /// </para>
   /// <para>
   /// As well as the PostSharp samples, this was heavily inspired by Ruurd Boeke's articles and 
   /// code, which you can find at
   /// http://www.sitechno.com/Blog/IntroducingEntityFrameworkContribEasyIPocoImplementationV01.aspx.
   /// </para>
   [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
   [MulticastAttributeUsage(MulticastTargets.Class, AllowMultiple = false)]
   public sealed class CreateTwoWayAssociationsAttribute : CompoundAspect
   {
      #region Fields

      [NonSerialized] private int aspectPriority;

      #endregion

      #region Methods

      /// <summary>
      /// Provides new aspects.
      /// </summary>
      /// <param name="element">Element to which the current custom attribute is applied.</param>
      /// <param name="collection">Collection into which
      /// new aspects have to be added.</param>
public override void ProvideAspects(object element, LaosReflectionAspectCollection collection)
{
   var targetType = (Type)element;

   // Process one-way associations declared in the current type.
   foreach (var property in targetType.UnderlyingSystemType.GetProperties()
      .Where(info => info.DeclaredIn(targetType) && info.IsOneWayAssociation()))
   {
      // We have a one-way association property, we now add aspects that indicate 
      // what the weaver should create on both the declaring type and the linked type.
      var fieldName = GetGeneratedFieldName(property);

      // In the linked type we want to create a field for the other end of the association.
      collection.AddAspect(
         property.GetCollectionTargetType(),
         new OtherEndFieldSubAspect(
            targetType.FullName,
         //GetCollectionTargetTypeName(property), 
            fieldName,
            fieldName));

      // In the type holding the collection we want to add an association attribute.
      collection.AddAspect(targetType, new AssociationAttributeSubAspect(property.Name, fieldName));
   }
}

      /// <summary>
      /// Gets the name of the generated field.
      /// </summary>
      /// <param name="property">The property.</param>
      /// <returns></returns>
      private string GetGeneratedFieldName(PropertyInfo property)
      {
         return string.Format("GeneratedFor_{0}_{1}", property.DeclaringType.Name, property.Name);
      }

      #endregion

      #region Properties

      /// <summary>
      /// Gets or sets the priority of the property-level aspect.
      /// </summary>
      /// <remarks>
      /// Give a large number to have the event raisen after any other
      /// on-success aspect on the properties of this type. The default value
      /// is 0.
      /// </remarks>
      public int AspectPriority
      {
         get { return aspectPriority; }
         set { aspectPriority = value; }
      }

      #endregion
   }
}